1   package org.apache.lucene.util;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one or more
5    * contributor license agreements.  See the NOTICE file distributed with
6    * this work for additional information regarding copyright ownership.
7    * The ASF licenses this file to You under the Apache License, Version 2.0
8    * (the "License"); you may not use this file except in compliance with
9    * the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  
20  import java.util.Arrays;
21  
22  /**
23   * A pool for int blocks similar to {@link ByteBlockPool}
24   * @lucene.internal
25   */
26  public final class IntBlockPool {
27    public final static int INT_BLOCK_SHIFT = 13;
28    public final static int INT_BLOCK_SIZE = 1 << INT_BLOCK_SHIFT;
29    public final static int INT_BLOCK_MASK = INT_BLOCK_SIZE - 1;
30    
31    /** Abstract class for allocating and freeing int
32     *  blocks. */
33    public abstract static class Allocator {
34      protected final int blockSize;
35  
36      public Allocator(int blockSize) {
37        this.blockSize = blockSize;
38      }
39  
40      public abstract void recycleIntBlocks(int[][] blocks, int start, int end);
41  
42      public int[] getIntBlock() {
43        return new int[blockSize];
44      }
45    }
46    
47    /** A simple {@link Allocator} that never recycles. */
48    public static final class DirectAllocator extends Allocator {
49  
50      /**
51       * Creates a new {@link DirectAllocator} with a default block size
52       */
53      public DirectAllocator() {
54        super(INT_BLOCK_SIZE);
55      }
56  
57      @Override
58      public void recycleIntBlocks(int[][] blocks, int start, int end) {
59      }
60    }
61    
62    /** array of buffers currently used in the pool. Buffers are allocated if needed don't modify this outside of this class */
63    public int[][] buffers = new int[10][];
64  
65    /** index into the buffers array pointing to the current buffer used as the head */
66    private int bufferUpto = -1;   
67    /** Pointer to the current position in head buffer */
68    public int intUpto = INT_BLOCK_SIZE;
69    /** Current head buffer */
70    public int[] buffer;
71    /** Current head offset */
72    public int intOffset = -INT_BLOCK_SIZE;
73  
74    private final Allocator allocator;
75  
76    /**
77     * Creates a new {@link IntBlockPool} with a default {@link Allocator}.
78     * @see IntBlockPool#nextBuffer()
79     */
80    public IntBlockPool() {
81      this(new DirectAllocator());
82    }
83    
84    /**
85     * Creates a new {@link IntBlockPool} with the given {@link Allocator}.
86     * @see IntBlockPool#nextBuffer()
87     */
88    public IntBlockPool(Allocator allocator) {
89      this.allocator = allocator;
90    }
91    
92    /**
93     * Resets the pool to its initial state reusing the first buffer. Calling
94     * {@link IntBlockPool#nextBuffer()} is not needed after reset.
95     */
96    public void reset() {
97      this.reset(true, true);
98    }
99    
100   /**
101    * Expert: Resets the pool to its initial state reusing the first buffer. 
102    * @param zeroFillBuffers if <code>true</code> the buffers are filled with <tt>0</tt>. 
103    *        This should be set to <code>true</code> if this pool is used with 
104    *        {@link SliceWriter}.
105    * @param reuseFirst if <code>true</code> the first buffer will be reused and calling
106    *        {@link IntBlockPool#nextBuffer()} is not needed after reset iff the 
107    *        block pool was used before ie. {@link IntBlockPool#nextBuffer()} was called before.
108    */
109   public void reset(boolean zeroFillBuffers, boolean reuseFirst) {
110     if (bufferUpto != -1) {
111       // We allocated at least one buffer
112 
113       if (zeroFillBuffers) {
114         for(int i=0;i<bufferUpto;i++) {
115           // Fully zero fill buffers that we fully used
116           Arrays.fill(buffers[i], 0);
117         }
118         // Partial zero fill the final buffer
119         Arrays.fill(buffers[bufferUpto], 0, intUpto, 0);
120       }
121      
122       if (bufferUpto > 0 || !reuseFirst) {
123         final int offset = reuseFirst ? 1 : 0;  
124         // Recycle all but the first buffer
125         allocator.recycleIntBlocks(buffers, offset, 1+bufferUpto);
126         Arrays.fill(buffers, offset, bufferUpto+1, null);
127       }
128       if (reuseFirst) {
129         // Re-use the first buffer
130         bufferUpto = 0;
131         intUpto = 0;
132         intOffset = 0;
133         buffer = buffers[0];
134       } else {
135         bufferUpto = -1;
136         intUpto = INT_BLOCK_SIZE;
137         intOffset = -INT_BLOCK_SIZE;
138         buffer = null;
139       }
140     }
141   }
142   
143   /**
144    * Advances the pool to its next buffer. This method should be called once
145    * after the constructor to initialize the pool. In contrast to the
146    * constructor a {@link IntBlockPool#reset()} call will advance the pool to
147    * its first buffer immediately.
148    */
149   public void nextBuffer() {
150     if (1+bufferUpto == buffers.length) {
151       int[][] newBuffers = new int[(int) (buffers.length*1.5)][];
152       System.arraycopy(buffers, 0, newBuffers, 0, buffers.length);
153       buffers = newBuffers;
154     }
155     buffer = buffers[1+bufferUpto] = allocator.getIntBlock();
156     bufferUpto++;
157 
158     intUpto = 0;
159     intOffset += INT_BLOCK_SIZE;
160   }
161   
162   /**
163    * Creates a new int slice with the given starting size and returns the slices offset in the pool.
164    * @see SliceReader
165    */
166   private int newSlice(final int size) {
167     if (intUpto > INT_BLOCK_SIZE-size) {
168       nextBuffer();
169       assert assertSliceBuffer(buffer);
170     }
171       
172     final int upto = intUpto;
173     intUpto += size;
174     buffer[intUpto-1] = 1;
175     return upto;
176   }
177   
178   private static final boolean assertSliceBuffer(int[] buffer) {
179     int count = 0;
180     for (int i = 0; i < buffer.length; i++) {
181       count += buffer[i]; // for slices the buffer must only have 0 values
182     }
183     return count == 0;
184   }
185   
186   
187   // no need to make this public unless we support different sizes
188   // TODO make the levels and the sizes configurable
189   /**
190    * An array holding the offset into the {@link IntBlockPool#LEVEL_SIZE_ARRAY}
191    * to quickly navigate to the next slice level.
192    */
193   private final static int[] NEXT_LEVEL_ARRAY = {1, 2, 3, 4, 5, 6, 7, 8, 9, 9};
194   
195   /**
196    * An array holding the level sizes for int slices.
197    */
198   private final static int[] LEVEL_SIZE_ARRAY = {2, 4, 8, 16, 32, 64, 128, 256, 512, 1024};
199   
200   /**
201    * The first level size for new slices
202    */
203   private final static int FIRST_LEVEL_SIZE = LEVEL_SIZE_ARRAY[0];
204 
205   /**
206    * Allocates a new slice from the given offset
207    */
208   private int allocSlice(final int[] slice, final int sliceOffset) {
209     final int level = slice[sliceOffset];
210     final int newLevel = NEXT_LEVEL_ARRAY[level-1];
211     final int newSize = LEVEL_SIZE_ARRAY[newLevel];
212     // Maybe allocate another block
213     if (intUpto > INT_BLOCK_SIZE-newSize) {
214       nextBuffer();
215       assert assertSliceBuffer(buffer);
216     }
217 
218     final int newUpto = intUpto;
219     final int offset = newUpto + intOffset;
220     intUpto += newSize;
221     // Write forwarding address at end of last slice:
222     slice[sliceOffset] = offset;
223         
224     // Write new level:
225     buffer[intUpto-1] = newLevel;
226 
227     return newUpto;
228   }
229   
230   /**
231    * A {@link SliceWriter} that allows to write multiple integer slices into a given {@link IntBlockPool}.
232    * 
233    *  @see SliceReader
234    *  @lucene.internal
235    */
236   public static class SliceWriter {
237     
238     private int offset;
239     private final IntBlockPool pool;
240     
241     
242     public SliceWriter(IntBlockPool pool) {
243       this.pool = pool;
244     }
245     /**
246      * 
247      */
248     public void reset(int sliceOffset) {
249       this.offset = sliceOffset;
250     }
251     
252     /**
253      * Writes the given value into the slice and resizes the slice if needed
254      */
255     public void writeInt(int value) {
256       int[] ints = pool.buffers[offset >> INT_BLOCK_SHIFT];
257       assert ints != null;
258       int relativeOffset = offset & INT_BLOCK_MASK;
259       if (ints[relativeOffset] != 0) {
260         // End of slice; allocate a new one
261           relativeOffset = pool.allocSlice(ints, relativeOffset);
262         ints = pool.buffer;
263         offset = relativeOffset + pool.intOffset;
264       }
265       ints[relativeOffset] = value;
266       offset++; 
267     }
268     
269     /**
270      * starts a new slice and returns the start offset. The returned value
271      * should be used as the start offset to initialize a {@link SliceReader}.
272      */
273     public int startNewSlice() {
274       return offset = pool.newSlice(FIRST_LEVEL_SIZE) + pool.intOffset;
275       
276     }
277     
278     /**
279      * Returns the offset of the currently written slice. The returned value
280      * should be used as the end offset to initialize a {@link SliceReader} once
281      * this slice is fully written or to reset the this writer if another slice
282      * needs to be written.
283      */
284     public int getCurrentOffset() {
285       return offset;
286     }
287   }
288   
289   /**
290    * A {@link SliceReader} that can read int slices written by a {@link SliceWriter}
291    * @lucene.internal
292    */
293   public static final class SliceReader {
294     
295     private final IntBlockPool pool;
296     private int upto;
297     private int bufferUpto;
298     private int bufferOffset;
299     private int[] buffer;
300     private int limit;
301     private int level;
302     private int end;
303     
304     /**
305      * Creates a new {@link SliceReader} on the given pool
306      */
307     public SliceReader(IntBlockPool pool) {
308       this.pool = pool;
309     }
310 
311     /**
312      * Resets the reader to a slice give the slices absolute start and end offset in the pool
313      */
314     public void reset(int startOffset, int endOffset) {
315       bufferUpto = startOffset / INT_BLOCK_SIZE;
316       bufferOffset = bufferUpto * INT_BLOCK_SIZE;
317       this.end = endOffset;
318       upto = startOffset;
319       level = 1;
320       
321       buffer = pool.buffers[bufferUpto];
322       upto = startOffset & INT_BLOCK_MASK;
323 
324       final int firstSize = IntBlockPool.LEVEL_SIZE_ARRAY[0];
325       if (startOffset+firstSize >= endOffset) {
326         // There is only this one slice to read
327         limit = endOffset & INT_BLOCK_MASK;
328       } else {
329         limit = upto+firstSize-1;
330       }
331 
332     }
333     
334     /**
335      * Returns <code>true</code> iff the current slice is fully read. If this
336      * method returns <code>true</code> {@link SliceReader#readInt()} should not
337      * be called again on this slice.
338      */
339     public boolean endOfSlice() {
340       assert upto + bufferOffset <= end;
341       return upto + bufferOffset == end;
342     }
343     
344     /**
345      * Reads the next int from the current slice and returns it.
346      * @see SliceReader#endOfSlice()
347      */
348     public int readInt() {
349       assert !endOfSlice();
350       assert upto <= limit;
351       if (upto == limit)
352         nextSlice();
353       return buffer[upto++];
354     }
355     
356     private void nextSlice() {
357       // Skip to our next slice
358       final int nextIndex = buffer[limit];
359       level = NEXT_LEVEL_ARRAY[level-1];
360       final int newSize = LEVEL_SIZE_ARRAY[level];
361 
362       bufferUpto = nextIndex / INT_BLOCK_SIZE;
363       bufferOffset = bufferUpto * INT_BLOCK_SIZE;
364 
365       buffer = pool.buffers[bufferUpto];
366       upto = nextIndex & INT_BLOCK_MASK;
367 
368       if (nextIndex + newSize >= end) {
369         // We are advancing to the final slice
370         assert end - nextIndex > 0;
371         limit = end - bufferOffset;
372       } else {
373         // This is not the final slice (subtract 4 for the
374         // forwarding address at the end of this new slice)
375         limit = upto+newSize-1;
376       }
377     }
378   }
379 }
380